package org.peterf.foxish.math.transformation;

import android.util.Log;

public class SpriteMatrix extends Matrix2D {

    protected float translateX;
    
    protected float translateY;
    
    protected float rotation;
    
    protected float rotationPX;
    
    protected float rotationPY;
    
    protected float scalePX;
    
    protected float scalePY;
    
    protected float scaleX;
    
    protected float scaleY;
    
    protected float skewPX;
    
    protected float skewPY;
    
    protected float skewX;
    
    protected float skewY;
        
    private static final int MASK_ROTATE = 0x1;
    
    private static final int MASK_SCALE = 0x2;
    
    private static final int MASK_SKEW = 0x3;
    
    private static final int MASK_TRANSLATE = 0x4;
    
    protected int orderMask = 0;
    
    protected boolean valid = true;
    
    private SpriteMatrix linkedReverse = null;
    
    @Override
    public float[] getMatrix() {
        refactor();
        return m;
    }
    
    public void apply(float[] vec) {
        refactor();
        super.apply(vec);
    }
    
    public SpriteMatrix getReversed(boolean linked) {
        SpriteMatrix r = new SpriteMatrix();
        for (int mask = orderMask; mask != 0; mask >>>= 4) {
            r.orderMask = (r.orderMask << 4) | (mask & 0xf);
        }
        r.rotation = -rotation;
        r.rotationPX = rotationPX;
        r.rotationPY = rotationPY;
        r.scalePX = scalePX;
        r.scalePY = scalePY;
        r.scaleX = 1.0f / scaleX;
        r.scaleY = 1.0f / scaleY;
        r.skewPX = skewPX;
        r.skewPY = skewPY;
        r.skewX = -skewX;
        r.skewY = -skewY;
        r.translateX = -translateX;
        r.translateY = -translateY;
        r.valid = false;
        if (linked) {
            linkedReverse = r;
        }
        return r;
    }
    
    public void debug() {
        //refactor();
        for (int i = 0; i < 4; ++i) {
            String s = "";
            for (int j = 0; j < 4; ++j) {
                s = s + m[j * 4 + i] + " ";
            }
            Log.i("peterfff", s);
        }
        //Log.i("peterfff", " ");
        Log.i("peterfff", Integer.toHexString(orderMask));
    }
    
    @Override
    public SpriteMatrix postRotation(float radian) {
        postMask(MASK_ROTATE);
        rotation = radian;
        rotationPX = 0.0f;
        rotationPY = 0.0f;
        valid = false;
        if (linkedReverse != null) {
            linkedReverse.preRotation(-radian);
        }
        return this;
    }
    
    @Override
    public SpriteMatrix preRotation(float radian) {
        preMask(MASK_ROTATE);
        rotation = radian;
        rotationPX = 0.0f;
        rotationPY = 0.0f;
        valid = false;
        if (linkedReverse != null) {
            linkedReverse.postRotation(-radian);
        }
        return this;
    }
    
    @Override
    public SpriteMatrix postRotation(float radian, float px, float py) {
        postMask(MASK_ROTATE);
        rotation = radian;
        rotationPX = px;
        rotationPY = py;
        valid = false;
        if (linkedReverse != null) {
            linkedReverse.preRotation(-radian, px, py);
        }
        return this;
    }
    
    @Override
    public SpriteMatrix preRotation(float radian, float px, float py) {
        preMask(MASK_ROTATE);
        rotation = radian;
        rotationPX = px;
        rotationPY = py;
        valid = false;
        if (linkedReverse != null) {
            linkedReverse.postRotation(-radian, px, py);
        }
        return this;
    }

    @Override
    public SpriteMatrix postScale(float x, float y) {
        postMask(MASK_SCALE);
        scaleX = x;
        scaleY = y;
        scalePX = 0.0f;
        scalePY = 0.0f;
        valid = false;
        if (linkedReverse != null) {
            linkedReverse.preScale(1.0f / x, 1.0f / y);
        }
        return this;
    }
    
    @Override
    public SpriteMatrix preScale(float x, float y) {
        preMask(MASK_SCALE);
        scaleX = x;
        scaleY = y;
        scalePX = 0.0f;
        scalePY = 0.0f;
        valid = false;
        if (linkedReverse != null) {
            linkedReverse.postScale(1.0f / x, 1.0f / y);
        }
        return this;
    }

    @Override
    public SpriteMatrix postScale(float x, float y, float px, float py) {
        postMask(MASK_SCALE);
        scaleX = x;
        scaleY = y;
        scalePX = px;
        scalePY = py;
        valid = false;
        if (linkedReverse != null) {
            linkedReverse.preScale(1.0f / x, 1.0f / y, px, py);
        }
        return this;
    }
    
    @Override
    public SpriteMatrix preScale(float x, float y, float px, float py) {
        preMask(MASK_SCALE);
        scaleX = x;
        scaleY = y;
        scalePX = px;
        scalePY = py;
        valid = false;
        if (linkedReverse != null) {
            linkedReverse.postScale(1.0f / x, 1.0f / y, px, py);
        }
        return this;
    }

    @Override
    public SpriteMatrix postSkew(float x, float y) {
        postMask(MASK_SKEW);
        skewX = x;
        skewY = y;
        skewPX = 0.0f;
        skewPY = 0.0f;
        valid = false;
        if (linkedReverse != null) {
            linkedReverse.preSkew(-x, -y);
        }
        return this;
    }

    @Override
    public SpriteMatrix preSkew(float x, float y) {
        preMask(MASK_SKEW);
        skewX = x;
        skewY = y;
        skewPX = 0.0f;
        skewPY = 0.0f;
        valid = false;
        if (linkedReverse != null) {
            linkedReverse.postSkew(-x, -y);
        }
        return this;
    }
    
    @Override
    public SpriteMatrix postSkew(float x, float y, float px, float py) {
        postMask(MASK_SKEW);
        skewX = x;
        skewY = y;
        skewPX = px;
        skewPY = py;
        valid = false;
        if (linkedReverse != null) {
            linkedReverse.preSkew(-x, -y, px, py);
        }
        return this;
    }
    
    @Override
    public SpriteMatrix preSkew(float x, float y, float px, float py) {
        preMask(MASK_SKEW);
        skewX = x;
        skewY = y;
        skewPX = px;
        skewPY = py;
        valid = false;
        if (linkedReverse != null) {
            linkedReverse.postSkew(-x, -y, px, py);
        }
        return this;
    }

    @Override
    public SpriteMatrix postTranslate(float x, float y) {
        postMask(MASK_TRANSLATE);
        translateX = x;
        translateY = y;
        valid = false;
        if (linkedReverse != null) {
            linkedReverse.preTranslate(-x, -y);
        }
        return this;
    }
    
    @Override
    public SpriteMatrix preTranslate(float x, float y) {
        preMask(MASK_TRANSLATE);
        translateX = x;
        translateY = y;
        valid = false;
        if (linkedReverse != null) {
            linkedReverse.postTranslate(-x, -y);
        }
        return this;
    }

    @Override
    public SpriteMatrix setIdentity() {
        super.setIdentity();
        valid = true;
        orderMask = 0;
        if (linkedReverse != null) {
            linkedReverse.setIdentity();
        }
        return this;
    }

    @Override
    public SpriteMatrix setRotation(float radian) {
        rotation = radian;
        rotationPX = 0.0f;
        rotationPY = 0.0f;
        if (existMask(MASK_ROTATE)) {
            valid = false;
        } else {
            orderMask = MASK_ROTATE;
            valid = true;
            super.setRotation(radian);
        }
        if (linkedReverse != null) {
            linkedReverse.setRotation(-radian);
        }
        return this;
    }

    @Override
    public SpriteMatrix setRotation(float radian, float px, float py) {
        rotation = radian;
        rotationPX = px;
        rotationPY = py;
        if (existMask(MASK_ROTATE)) {
            valid = false;
        } else {
            orderMask = MASK_ROTATE;
            valid = true;
            super.setRotation(radian, px, py);
        }
        if (linkedReverse != null) {
            linkedReverse.setRotation(-radian, px, py);
        }
        return this;
    }

    @Override
    public SpriteMatrix setScale(float x, float y) {
        scaleX = x;
        scaleY = y;
        scalePX = 0.0f;
        scalePY = 0.0f;
        if (existMask(MASK_SCALE)) {
            valid = false;
        } else {
            orderMask = MASK_SCALE;
            valid = true;
            super.setScale(x, y);
        }
        if (linkedReverse != null) {
            linkedReverse.setScale(1.0f / x, 1.0f / y);
        }
        return this;
    }

    @Override
    public SpriteMatrix setScale(float x, float y, float px, float py) {
        scaleX = x;
        scaleY = y;
        scalePX = px;
        scalePY = py;
        if (existMask(MASK_SCALE)) {
            valid = false;
        } else {
            orderMask = MASK_SCALE;
            valid = true;
            super.setScale(x, y, px, py);
        }
        if (linkedReverse != null) {
            linkedReverse.setScale(1.0f / x, 1.0f / y, px, py);
        }
        return this;
    }

    @Override
    public SpriteMatrix setSkew(float x, float y) {
        skewX = x;
        skewY = y;
        skewPX = 0.0f;
        skewPY = 0.0f;
        if (existMask(MASK_SKEW)) {
            valid = false;
        } else {
            orderMask = MASK_SKEW;
            valid = true;
            super.setSkew(x, y);
        }
        if (linkedReverse != null) {
            linkedReverse.setSkew(-x, -y);
        }
        return this;
    }

    @Override
    public SpriteMatrix setSkew(float x, float y, float px, float py) {
        skewX = x;
        skewY = y;
        skewPX = px;
        skewPY = py;
        if (existMask(MASK_SKEW)) {
            valid = false;
        } else {
            orderMask = MASK_SKEW;
            valid = true;
            super.setSkew(x, y, px, py);
        }
        if (linkedReverse != null) {
            linkedReverse.setSkew(-x, -y, px, py);
        }
        return this;
    }
    
    @Override
    public SpriteMatrix setTranslate(float x, float y) {
        translateX = x;
        translateY = y;
        if (existMask(MASK_TRANSLATE)) {
            valid = false;
        } else {
            orderMask = MASK_TRANSLATE;
            valid = true;
            super.setTranslate(x, y);
        }
        if (linkedReverse != null) {
            linkedReverse.setTranslate(-x, -y);
        }
        return this;
    }
    
    private void postMask(int type) {
        int pos = 0;
        for (int mask = orderMask; mask != 0; mask >>>= 4) {
            if (type == (mask & 0xf)) {
                return;
            }
            pos += 4;
        }
        orderMask |= (type << pos);
    }
    
    private void preMask(int type) {
        for (int mask = orderMask; mask != 0; mask >>>= 4) {
            if (type == (mask & 0xf)) {
                return;
            }
        }
        orderMask = (orderMask << 4) | type;
    }
     
    private boolean existMask(int type) {
        for (int mask = orderMask; mask != 0; mask >>>= 4) {
            if (type == (mask & 0xf)) {
                return true;
            }
        }
        return false;
    }
    
    public void refactor() {
        if (valid) {
            return;
        }
        int mask = orderMask;
        switch (mask & 0xf) {
        case MASK_ROTATE:
            if (rotationPX == 0.0f && rotationPY == 0.0f) {
                super.setRotation(rotation);
            } else {
                super.setRotation(rotation, rotationPX, rotationPY);
            }
            break;
        case MASK_SCALE: 
            if (scalePX == 0.0f && scalePY == 0.0f) {
                super.setScale(scaleX, scaleY);
            } else {
                super.setScale(scaleX, scaleY, scalePX, scalePY);
            }
            break;
        case MASK_SKEW:
            if (skewPX == 0.0f && skewPY == 0.0f) {
                super.setSkew(skewX, skewY);
            } else {
                super.setSkew(skewX, skewY, skewPX, skewPY);
            }
            break;
        case MASK_TRANSLATE:
            super.setTranslate(translateX, translateY);
            break;
        }
        for (mask >>>= 4; mask != 0; mask >>>= 4) {
            switch (mask & 0xf) {
            case MASK_ROTATE:
                if (rotationPX == 0.0f && rotationPY == 0.0f) {
                    super.postRotation(rotation);
                } else {
                    super.postRotation(rotation, rotationPX, rotationPY);
                }
                break;
            case MASK_SCALE: 
                if (scalePX == 0.0f && scalePY == 0.0f) {
                    super.postScale(scaleX, scaleY);
                } else {
                    super.postScale(scaleX, scaleY, scalePX, scalePY);
                }
                break;
            case MASK_SKEW:
                if (skewPX == 0.0f && skewPY == 0.0f) {
                    super.postSkew(skewX, skewY);
                } else {
                    super.postSkew(skewX, skewY, skewPX, skewPY);
                }
                break;
            case MASK_TRANSLATE:
                super.postTranslate(translateX, translateY);
                break;
            }
        }
        valid = true;
    }
    
}
